/*******************************************************************************
 * Copyright (c) 2007, Geophysical Center RAS
 * Sergey Berezin, Dmitry Voitsekhovskiy
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms,
 * with or without modification, are permitted provided
 * that the following conditions are met:
 *
 * - Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 *
 * - Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * - Neither the name of the Geophysical Center RAS nor the names
 * of its contributors may be used to endorse or promote products
 * derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
 * THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ******************************************************************************/
using System;

namespace VisualEsse.DataEngine.Types
{
    public class DataField<GridType,ArrayType>
    {
        protected GridType grid;
        protected ArrayType data;

        public DataField(GridType grid, ArrayType data)
        {
            this.grid = grid;
            this.data = data;
        }

        public GridType Grid
        {
            get
            {
                return grid;
            }
        }

        public ArrayType Data
        {
            get
            {
                return data;
            }
        }
    }
   
    public class DataField3d<GridType,ArrayType> : DataField<GridType, ArrayType> 
        where ArrayType : IArray3d
        where GridType : IGrid3d 
    {
        public DataField3d(GridType grid, ArrayType data) : base(grid,data)
        {
            if (grid.Width != data.Width || grid.Height != data.Height || grid.Depth != data.Depth)
                throw new ArgumentException("DataField3d: data dimensions do not match");
        }
    }

    public class DataField2d<GridType, ArrayType> : DataField<GridType, ArrayType>
        where ArrayType : IArray2d
        where GridType : IGrid2d
    {
        public DataField2d(GridType grid, ArrayType data)
            : base(grid, data)
        {
            if (grid.Width != data.Width || grid.Height != data.Height)
                throw new ArgumentException("DataField2d: data dimensions do not match");
        }
    }

    public class UniformVector2dField2d : DataField2d<IUniformGrid2d, IVector2dArray2d>
    {
        public UniformVector2dField2d(IUniformGrid2d grid, IVector2dArray2d data):
            base(grid, data)
        {
        }
    }

    public class GeoUniformVector2dField2d : UniformVector2dField2d
    {
        public GeoUniformVector2dField2d(GeoUniformGrid2d grid, IVector2dArray2d data)
            : base(grid, data)
        {
        }

        public GeoUniformGrid2d GeoGrid
        {
            get
            {
                return (GeoUniformGrid2d)this.grid;
            }
        }
    }


    public class NonUniformScalarField2d : DataField2d<INonUniformGrid2d, IScalarArray2d>
    {
        public NonUniformScalarField2d(INonUniformGrid2d grid, IScalarArray2d data)
            : base(grid, data)
        {
        }
    }

    public class UniformScalarField2d : DataField2d<IUniformGrid2d, IScalarArray2d>
    {
        public UniformScalarField2d(IUniformGrid2d grid, IScalarArray2d data)
            : base(grid, data)
        {
        }
    }

    public class GeoUniformScalarField2d : UniformScalarField2d
    {
        public GeoUniformScalarField2d(GeoUniformGrid2d geoGrid, IScalarArray2d data)
            : base(geoGrid, data)
        {
        }

        public GeoUniformGrid2d GeoGrid
        {
            get
            {
                return (GeoUniformGrid2d)this.grid;
            }
        }

        public GeoUniformScalarField2d SelectData(GeoRect selection)
        {
            GeoUniformGrid2d geoGrid = this.grid as GeoUniformGrid2d;

            float latDist, longDist;
            GeoPoint.CircleDistance(geoGrid.FirstPoint, selection.LowerLeft, out latDist, out longDist);
            int startI = (int)Math.Floor(longDist / geoGrid.LongStep);
            int startJ = (int)Math.Floor(latDist / geoGrid.LatStep);

            GeoPoint.CircleDistance(geoGrid.FirstPoint, selection.UpperRight, out latDist, out longDist);
            int endI = (int)Math.Ceiling(longDist / geoGrid.LongStep);
            int endJ = (int)Math.Ceiling(latDist / geoGrid.LatStep);
            if (endI >= geoGrid.Width)
                endI = geoGrid.Width - 1;
            if (endJ >= geoGrid.Height)
                endJ = geoGrid.Height - 1;
                        

            UniformGrid1d sgLong = new UniformGrid1d(endI - startI + 1, geoGrid.Longitudes.Knots[startI], geoGrid.LongStep);
            UniformGrid1d sgLat = new UniformGrid1d(endJ - startJ + 1, geoGrid.Latitudes.Knots[startJ], geoGrid.LatStep);
            GeoUniformGrid2d selGrid = new GeoUniformGrid2d(sgLong, sgLat);

            ScalarArray2d selData = new ScalarArray2d(sgLong.Length, sgLat.Length);
            double[,] a = selData.Data;

            for (int i = 0; startI <= endI; startI++, i++)
                for (int j = 0, J = startJ; J <= endJ; J++, j++)
                {
                    a[i, j] = data[startI, J];
                }

            GeoUniformScalarField2d selField = new GeoUniformScalarField2d(selGrid, selData);
            return selField;

        }
    }
}